package com.alpaak.hellospringboot.utils.log;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alpaak.hellospringboot.utils.IPUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.Enumeration;
import java.util.Objects;

/**
 * @ClassName: ReqLogAspect
 * @Description: 请求日志
 * @author: Alpaak
 * @date: 2021/07/08 15:38
 */

@Aspect
@Component
public class ReqLogAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(ReqLogAspect.class);

    private final static String REQ_TYPE_FILE = "file";
    private final static String REQ_TYPE_FILES = "files";

    @Pointcut("execution(* com.alpaak.hellospringboot.controller..*(..))")
    public void controller() {
    }


    @Around("controller()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        //记录请求开始执行时间：
        long beginTime = System.currentTimeMillis();

        RequestInfo request = resolve(pjp);
        //记录日志
        LOGGER.info("请求开始!\n{}", request);
        Object result = pjp.proceed();

        //请求操作成功
        String resultJson = "";
        if (result != null) {
//            if (result instanceof Result) {
//                resultJson = JSON.toJSONString(result, SerializerFeature.WriteDateUseDateFormat);
//            } else {
//                resultJson = String.valueOf(result);
//            }
            resultJson = JSON.toJSONString(result, SerializerFeature.WriteDateUseDateFormat);
        }

        //记录请求完成执行时间：
        long endTime = System.currentTimeMillis();
        long usedTime = endTime - beginTime;

        //记录日志
        LOGGER.info("{} \n请求成功! 耗时:[{}]ms.", resultJson, usedTime);

        // TODO Save to DB
        return result;
    }

    private RequestInfo resolve(ProceedingJoinPoint pjp) {
        //获取请求信息
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(sra).getRequest();

        Enumeration<String> headerNames = request.getHeaderNames();
        StringBuilder headers = new StringBuilder();
        while (headerNames.hasMoreElements()) {
            headers.append(headerNames.nextElement()).append(",");
        }
        LOGGER.debug("====> Headers: {}", headers.toString());

        //获取代理地址、请求地址、请求类名、方法名
        //获取请求参数
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        //获取请求参数类型
        String[] parameterNames = ms.getParameterNames();
        //获取请求参数值
        Object[] parameterValues = pjp.getArgs();
        StringBuilder params = new StringBuilder();
        //组合请求参数，进行日志打印
        if (parameterNames != null && parameterNames.length > 0) {
            for (int i = 0; i < parameterNames.length; i++) {
                String name = parameterNames[i];
                if ("bindingResult".equals(name)) {
                    break;
                }

                // 文件
                if (REQ_TYPE_FILE.equals(name.toLowerCase()) ||
                        REQ_TYPE_FILES.equals(name.toLowerCase())) {
                    continue;
                }

                Object value = parameterValues[i];
                if ((value instanceof HttpServletRequest) || (value instanceof HttpServletResponse)) {
                    params.append("{").append(name).append(":").append(value).append("}");
                } else {
                    params.append("{").append(name).append(":")
                            .append(JSON.toJSONString(value, SerializerFeature.WriteDateUseDateFormat))
                            .append("}");
                }
            }
        }

        final RequestInfo.RequestInfoBuilder builder = RequestInfo.builder()
                .date(LocalDateTime.now().toString())
                .address(IPUtil.getClientIp(request))
                .uri(request.getRequestURI())
                .method(request.getMethod())
                .clazz(pjp.getTarget().getClass().getSimpleName() + "->" + pjp.getSignature().getName())
                .params(params.toString());

//        Optional.ofNullable(UserUtils.getUser())
//                .ifPresent(user -> builder.user(user.getName()));

        return builder.build();
    }

    static class RequestInfo {
        String client;
        String user;
        String address;
        String uri;
        String clazz;
        String method;
        String params;
        String date;


        private RequestInfo(RequestInfoBuilder b) {
            this.client = b.client;
            this.user = b.user;
            this.address = b.address;
            this.uri = b.uri;
            this.clazz = b.clazz;
            this.method = b.method;
            this.params = b.params;
            this.date = b.date;
        }

        public static RequestInfoBuilder builder() {
            return new RequestInfoBuilder();
        }

        @Override
        public String toString() {
            return String.format(
                    "%s %s\n==>|PARAMS：%s\n==>|IP: %s;\n==>|CLIENT: %s\n==>|USER: %s\n==>|CONTROLLER：%s\n==>|DATE: %s",
                    method, uri, params, address, client, user, clazz, date
            );
        }


        public static class RequestInfoBuilder {
            String client;
            String user;
            String address;
            String uri;
            String clazz;
            String method;
            String params;
            String date;

            private RequestInfoBuilder() {
            }

            public RequestInfoBuilder client(String client) {
                this.client = client;
                return this;
            }

            public RequestInfoBuilder user(String user) {
                this.user = user;
                return this;
            }

            public RequestInfoBuilder address(String address) {
                this.address = address;
                return this;
            }

            public RequestInfoBuilder uri(String uri) {
                this.uri = uri;
                return this;
            }

            public RequestInfoBuilder clazz(String clazz) {
                this.clazz = clazz;
                return this;
            }

            public RequestInfoBuilder method(String method) {
                this.method = method;
                return this;
            }

            public RequestInfoBuilder params(String params) {
                this.params = params;
                return this;
            }

            public RequestInfoBuilder date(String date) {
                this.date = date;
                return this;
            }

            public RequestInfo build() {
                return new RequestInfo(this);
            }
        }
    }

}
